#include <sys/param.h>
#include "esp_event.h"
#include "esp_log.h"
#include "esp_mac.h"
#include "nvs_flash.h"
#include "esp_wifi.h"
#include "esp_netif.h"
#include "lwip/inet.h"
#include "cJSON.h"
#include "esp_http_server.h"
#include "dns_server.h"
#include <stdio.h>
#include <string.h>
#include "esp_vfs_semihost.h"
#include "esp_vfs_fat.h"
#include "esp_spiffs.h"
#define ESP_WIFI_SSID "ESP32_WIFI"   
#define ESP_WIFI_PASS "123456789"
#define MAX_STA_CONN 4


static const char *TAG = "WIFI_WEB";   

//ESP_VFS_PATH_MAX  15
#define FILE_PATH_MAX (ESP_VFS_PATH_MAX + 128)
#define SCRATCH_BUFSIZE (10240)
typedef struct rest_server_context {
    char base_path[ESP_VFS_PATH_MAX + 1];
    char scratch[SCRATCH_BUFSIZE];
} rest_server_context_t;

//刷新界面
static void redirect_index_html(httpd_req_t *req)
{
    httpd_resp_set_status(req, "307 Temporary Redirect");
    httpd_resp_set_hdr(req, "Location", "/");
	//Response body can be empty
    httpd_resp_send(req, NULL, 0);  
}

#define CHECK_FILE_EXTENSION(filename, ext) (strcasecmp(&filename[strlen(filename) - strlen(ext)], ext) == 0)

/* Set HTTP response content type according to file extension */
//设置HTTP响应内容类型  shell.html  //10-5=5 
static esp_err_t set_content_type_from_file(httpd_req_t* req, const char* filepath)
{
    const char* type = "text/plain";
    if (CHECK_FILE_EXTENSION(filepath, ".html")) {
        type = "text/html";
    } else if (CHECK_FILE_EXTENSION(filepath, ".js")) {
        type = "application/javascript";
    } else if (CHECK_FILE_EXTENSION(filepath, ".css")) {
        type = "text/css";
    } else if (CHECK_FILE_EXTENSION(filepath, ".png")) {
        type = "image/png";
    } else if (CHECK_FILE_EXTENSION(filepath, ".ico")) {
        type = "image/x-icon";
    } else if (CHECK_FILE_EXTENSION(filepath, ".svg")) {
        type = "text/xml";
    }
    return httpd_resp_set_type(req, type);
}


/* A help function to get post request data */
static esp_err_t recv_post_data(httpd_req_t* req, char* buf)
{
    int total_len = req->content_len;
    int cur_len = 0;
    int received = 0;
	printf("total_len=%d\r\n",total_len);
    if (total_len >= SCRATCH_BUFSIZE) {
        /* Respond with 500 Internal Server Error */
        httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "content too long");
        return ESP_FAIL;
    }
    while (cur_len < total_len) {
        received = httpd_req_recv(req, buf + cur_len, total_len);
		printf("received=%d\r\n",received);
        if (received <= 0) {
            /* Respond with 500 Internal Server Error */
            httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to post control value");
            return ESP_FAIL;
        }
        cur_len += received;
    }
    buf[total_len] = '\0';//now ,the post is str format, like ssid=yuxin&pwd=TestPWD&chl=1&ecn=0&maxconn=1&ssidhidden=0
    ESP_LOGI(TAG, "Post data is : %s\n", buf);
    return ESP_OK;
}



static esp_err_t update_values_post_handler(httpd_req_t* req)
{
	// return index html file
	ESP_LOGI(TAG, "uri: %s\n", req->uri);
	rest_server_context_t* rest_context = (rest_server_context_t*) req->user_ctx;
    char* buf = ((rest_server_context_t*) (req->user_ctx))->scratch;
	recv_post_data(req, buf);
    return ESP_OK;
}

/* Send HTTP response with the contents of the requested file */
static esp_err_t rest_common_get_handler(httpd_req_t* req)
{
	ESP_LOGI(TAG, "uri: %s\n", req->uri);
    char filepath[FILE_PATH_MAX];
    rest_server_context_t* rest_context = (rest_server_context_t*) req->user_ctx;
    strlcpy(filepath, rest_context->base_path, sizeof(filepath));
    if (req->uri[strlen(req->uri) - 1] == '/') {
        strlcat(filepath, "/web_image/index.html", sizeof(filepath));
    } else {
		strlcat(filepath, "/web_image", sizeof(filepath));
		if(strcmp(req->uri,"/ota")==0){
			strlcat(filepath, req->uri, sizeof(filepath));
			strlcat(filepath, ".html", sizeof(filepath));
		}
		else if(strcmp(req->uri,"/wifimanager")==0){
			strlcat(filepath, req->uri, sizeof(filepath));
			strlcat(filepath, ".html", sizeof(filepath));
		}
		else {
			strlcat(filepath, req->uri, sizeof(filepath));
		}        
    }

    char* p = strrchr(filepath, '?');
    if (p != NULL) {
        *p = '\0';
    }

    int fd = open(filepath, O_RDONLY, 0);
    if (fd == -1) {
        ESP_LOGE(TAG, "Failed to open file : %s", filepath);
        /* Respond with 500 Internal Server Error */
        httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to read existing file");
        return ESP_FAIL;
    }

    set_content_type_from_file(req, filepath);

    char* chunk = rest_context->scratch;
    ssize_t read_bytes;
    do {
        /* Read file in chunks into the scratch buffer */
        read_bytes = read(fd, chunk, SCRATCH_BUFSIZE);
        if (read_bytes == -1) {
            ESP_LOGE(TAG, "Failed to read file : %s", filepath);
        } else if (read_bytes > 0) {
            /* Send the buffer contents as HTTP response chunk */
            if (httpd_resp_send_chunk(req, chunk, read_bytes) != ESP_OK) {
                close(fd);
                ESP_LOGE(TAG, "File sending failed!");
                /* Abort sending file */
                httpd_resp_sendstr_chunk(req, NULL);
                /* Respond with 500 Internal Server Error */
                httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file");
                return ESP_FAIL;
            }
        }
    } while (read_bytes > 0);
    /* Close file after sending complete */
    close(fd);
    ESP_LOGI(TAG, "File sending complete");
    /* Respond with an empty chunk to signal HTTP response completion */
    httpd_resp_send_chunk(req, NULL, 0);
    return ESP_OK;
}

static const char web_base_point[] = "/spiffs";

esp_err_t init_fs(void)
{
    esp_vfs_spiffs_conf_t conf = {
        .base_path = web_base_point,     //文件挂载点路径
        .partition_label = NULL,         //分区标签，设为NULL表示默认分区
        .max_files = 5,                  //最大同时打开文件数量
        .format_if_mount_failed = false  //挂载失败时是否格式化分区，这里设为false
    };
	//注册并挂载 SPIFFS 文件系统
    esp_err_t ret = esp_vfs_spiffs_register(&conf);

    if (ret != ESP_OK) {
        if (ret == ESP_FAIL) {
            ESP_LOGE(TAG, "Failed to mount or format filesystem");
        } else if (ret == ESP_ERR_NOT_FOUND) {
            ESP_LOGE(TAG, "Failed to find SPIFFS partition");
        } else {
            ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret));
        }
        return ESP_FAIL;
    }

    size_t total = 0, used = 0;
	//获取分区的总大小和已使用空间，并打印信息
    ret = esp_spiffs_info(NULL, &total, &used);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to get SPIFFS partition information (%s)", esp_err_to_name(ret));
    } else {
        ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used);
    }
    return ESP_OK;
}

static void wifi_event_handler(void *arg, esp_event_base_t event_base,
                               int32_t event_id, void *event_data)
{
    if (event_id == WIFI_EVENT_AP_STACONNECTED) {
        wifi_event_ap_staconnected_t *event = (wifi_event_ap_staconnected_t *)event_data;
        ESP_LOGI(TAG, "station " MACSTR " join, AID=%d",
                 MAC2STR(event->mac), event->aid);
    } else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {
        wifi_event_ap_stadisconnected_t *event = (wifi_event_ap_stadisconnected_t *)event_data;
        ESP_LOGI(TAG, "station " MACSTR " leave, AID=%d",
                 MAC2STR(event->mac), event->aid);
    }
}

//WIFI AP模式初始化
static void wifi_init_softap(void)
{
	//使用默认配置初始化WIFI
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
	//WIFI初始化
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
	//注册回调函数
    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL));
	//WIFI信息结构体
    wifi_config_t wifi_config = {
        .ap = {
            .ssid = ESP_WIFI_SSID,                     //wifi 名字
            .ssid_len = strlen(ESP_WIFI_SSID),         //WiFi 名称长度
            .password = ESP_WIFI_PASS,                 //WiFi 密码
            .max_connection = MAX_STA_CONN,            //wifi 最大连接数量 
            .authmode = WIFI_AUTH_WPA_WPA2_PSK         //wifi 加密方式
        },
    };
    if (strlen(ESP_WIFI_PASS) == 0) {
        wifi_config.ap.authmode = WIFI_AUTH_OPEN;
    }

	//设置WIFI操作方式
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
	//设置AP的配置
    ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config));
	//启动按照当前的配置  
    ESP_ERROR_CHECK(esp_wifi_start());
	//IP数据结构体
    esp_netif_ip_info_t ip_info;
    esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_AP_DEF"), &ip_info);

    char ip_addr[16];
    inet_ntoa_r(ip_info.ip.addr,ip_addr, 16);
    ESP_LOGI(TAG, "Set up softAP with IP: %s", ip_addr);
}


// HTTP Error (404) Handler - Redirects all requests to the root page
esp_err_t http_404_error_handler(httpd_req_t *req, httpd_err_code_t err)
{
    // Set status
    httpd_resp_set_status(req, "302 Temporary Redirect");
    // Redirect to the "/" root directory
    httpd_resp_set_hdr(req, "Location", "/");
    // iOS requires content in the response to detect a captive portal, simply redirecting is not sufficient.
    httpd_resp_send(req, "Redirect to the captive portal", HTTPD_RESP_USE_STRLEN);
    return ESP_OK;
}

//启动Web服务器
static httpd_handle_t start_webserver(void)
{
    rest_server_context_t* rest_context = calloc(1, sizeof(rest_server_context_t));
    if(rest_context==NULL) 
		return NULL;
	ESP_LOGI(TAG, "rest_context success");
    strlcpy(rest_context->base_path, web_base_point, sizeof(rest_context->base_path));

    httpd_handle_t server = NULL;
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    config.max_open_sockets = 7;
	config.max_uri_handlers = 16;
    config.lru_purge_enable = true;

	httpd_uri_t httpd_uri_array[] = {
		{"/wifi_config", HTTP_POST, update_values_post_handler,rest_context},
		{"/", HTTP_GET, rest_common_get_handler,rest_context},
		{"/ota", HTTP_GET, rest_common_get_handler,rest_context},
		{"/wifimanager", HTTP_GET, rest_common_get_handler,rest_context},
		{"/style.css", HTTP_GET, rest_common_get_handler,rest_context},
		{"/jquery-3.3.1.min.js", HTTP_GET, rest_common_get_handler,rest_context},
		{"/script.js", HTTP_GET, rest_common_get_handler,rest_context},
		{"/favicon.ico", HTTP_GET, rest_common_get_handler,rest_context}
    };
    // Start the httpd server
    //启动httpd server服务器
    if (httpd_start(&server, &config) == ESP_OK) {

			for(int i = 0; i < sizeof(httpd_uri_array)/sizeof(httpd_uri_t); i++){
			if (httpd_register_uri_handler(server, &httpd_uri_array[i]) != ESP_OK) {
				ESP_LOGE(TAG, "httpd register uri_array[%d] fail", i);
			}
		}

    }
    return server;
}

void app_main(void)
{
	
    //设置 httpd_uri 到错误水平
    esp_log_level_set("httpd_uri", ESP_LOG_ERROR);
    esp_log_level_set("httpd_txrx", ESP_LOG_ERROR);
    esp_log_level_set("httpd_parse", ESP_LOG_ERROR);
    // Initialize networking stack
	//初始化网络堆栈
    ESP_ERROR_CHECK(esp_netif_init());

    //Create default event loop needed by the  main app
	//创建主应用程序所需要的事件循环
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    //Initialize NVS needed by Wi-Fi
	//初始化WIFI所需要的NVS
    ESP_ERROR_CHECK(nvs_flash_init());

    //Initialize Wi-Fi including netif with default config
	//用默认配置初始化包含netif的WI-FI
    esp_netif_create_default_wifi_ap();

    //Initialise ESP32 in SoftAP mode
	//初始化ESP32用AP模式
    wifi_init_softap();

	init_fs();

    // Start the server for the first time
	//首次启动服务器
    start_webserver();

    //Start the DNS server that will redirect all queries to the softAP IP
	//启动DNS服务器，将所有查询重定向到softAP IP
    dns_server_config_t config = DNS_SERVER_CONFIG_SINGLE("*" /* all A queries */, "WIFI_AP_DEF" /* softAP netif ID */);
	//启动DNS服务器
    start_dns_server(&config);

}
